@sapphire/shapeshift
ShapeShift
Blazing fast input validation and transformation ⚡
Description
A very fast and lightweight input validation and transformation library for JavaScript.
Note: ShapeShift requires Node.js v15.0.0 or higher to work.
Features
- TypeScript friendly
- Offers CJS, ESM and UMD builds
- API similar to
zod
- Faster than ⚡
Usage
For complete usages, please dive into our documentation
Basic usage
Creating a simple string schema
import { s } from '@sapphire/shapeshift';
const mySchema = s.string;
mySchema.parse('sapphire');
mySchema.parse(12);
Creating an object schema
import { s } from '@sapphire/shapeshift';
const user = s.object({
username: s.string
});
user.parse({ username: 'Sapphire' });
Defining schemas
Primitives
import { s } from '@sapphire/shapeshift';
s.string;
s.number;
s.bigint;
s.boolean;
s.date;
s.undefined;
s.null;
s.nullish;
s.any;
s.unknown;
s.never;
Literals
s.literal('sapphire');
s.literal(12);
s.literal(420n);
s.literal(true);
s.literal(new Date(1639278160000));
Strings
ShapeShift includes a handful of string-specific validations:
s.string.lengthLt(5);
s.string.lengthLe(5);
s.string.lengthGt(5);
s.string.lengthGe(5);
s.string.lengthEq(5);
s.string.lengthNe(5);
s.string.email;
s.string.url();
s.string.uuid();
s.string.regex(regex);
s.string.ip();
s.string.ipv4;
s.string.ipv6;
Numbers
ShapeShift includes a handful of number-specific validations:
s.number.gt(5);
s.number.ge(5);
s.number.lt(5);
s.number.le(5);
s.number.eq(5);
s.number.ne(5);
s.number.eq(NaN);
s.number.ne(NaN);
s.number.int;
s.number.safeInt;
s.number.finite;
s.number.positive;
s.number.negative;
s.number.divisibleBy(5);
And transformations:
s.number.abs;
s.number.sign;
s.number.trunc;
s.number.floor;
s.number.fround;
s.number.round;
s.number.ceil;
BigInts
ShapeShift includes a handful of number-specific validations:
s.bigint.gt(5n);
s.bigint.ge(5n);
s.bigint.lt(5n);
s.bigint.le(5n);
s.bigint.eq(5n);
s.bigint.ne(5n);
s.bigint.positive;
s.bigint.negative;
s.bigint.divisibleBy(5n);
And transformations:
s.bigint.abs;
s.bigint.intN(5);
s.bigint.uintN(5);
Booleans
ShapeShift includes a few boolean-specific validations:
s.boolean.true;
s.boolean.false;
s.boolean.eq(true);
s.boolean.eq(false);
s.boolean.ne(true);
s.boolean.ne(false);
Arrays
const stringArray = s.array(s.string);
const stringArray = s.string.array;
ShapeShift includes a handful of array-specific validations:
s.string.array.lengthLt(5);
s.string.array.lengthLe(5);
s.string.array.lengthGt(5);
s.string.array.lengthGe(5);
s.string.array.lengthEq(5);
s.string.array.lengthNe(5);
s.string.array.lengthRange(0, 4);
s.string.array.lengthRangeInclusive(0, 4);
s.string.array.lengthRangeExclusive(0, 4);
Note: All .length
methods define tuple types with the given amount of elements. For example, s.string.array.lengthGe(2)
's inferred type is [string, string, ...string[]]
Tuples
Unlike arrays, tuples have a fixed number of elements and each element can have a different type:
const dish = s.tuple([
s.string,
s.number.int,
s.date
]);
dish.parse(['Iberian ham', 10, new Date()]);
Objects
const animal = s.object({
name: s.string,
age: s.number
});
.extend
:
You can add additional fields using either an object or an ObjectValidator, in this case, you will get a new object validator with the merged properties:
const pet = animal.extend({
owner: s.string.nullish
});
const pet = animal.extend(
s.object({
owner: s.string.nullish
})
);
If both schemas share keys, an error will be thrown. Please use .omit
on the first object if you desire this behaviour.
.pick
/ .omit
:
Inspired by TypeScript's built-in Pick
and Omit
utility types, all object schemas have the aforementioned methods that return a modifier version:
const pkg = s.object({
name: s.string,
description: s.string,
dependencies: s.string.array
});
const justTheName = pkg.pick(['name']);
const noDependencies = pkg.omit(['dependencies']);
.partial
Inspired by TypeScript's built-in Partial
utility type, all object schemas have the aforementioned method that makes all properties optional:
const user = s.object({
username: s.string,
password: s.string
}).partial;
Which is the same as doing:
const user = s.object({
username: s.string.optional,
password: s.string.optional
});
Unrecognized keys
By default, ShapeShift will not include keys that are not defined by the schema during parsing:
const person = s.object({
framework: s.string
});
person.parse({
framework: 'Sapphire',
awesome: true
});
.strict
You can disallow unknown keys with .strict
. If the input includes any unknown keys, an error will be thrown.
const person = s.object({
framework: s.string
}).strict;
person.parse({
framework: 'Sapphire',
awesome: true
});
.ignore
You can use the .ignore
getter to reset an object schema to the default behaviour (ignoring unrecognized keys).
.passthrough
You can use the .passthrough
getter to make the validator add the unrecognized properties the shape does not have, from the input.
Records
Record schemas are similar to objects, but validate Record<string, T>
types, keep in mind this does not check for the keys, and cannot support validation for specific ones:
const tags = s.record(s.string);
tags.parse({ foo: 'bar', hello: 'world' });
tags.parse({ foo: 42 });
tags.parse('Hello');
Unions
ShapeShift includes a built-in method for composing OR types:
const stringOrNumber = s.union(s.string, s.number);
stringOrNumber.parse('Sapphire');
stringOrNumber.parse(42);
stringOrNumber.parse({});
Enums
Enums are a convenience method that aliases s.union(s.literal(a), s.literal(b), ...)
:
s.enum('Red', 'Green', 'Blue');
Maps
const map = s.map(s.string, s.number);
Sets
const set = s.set(s.number);
Instances
You can use s.instance(Class)
to check that the input is an instance of a class. This is useful to validate inputs against classes:
class User {
public constructor(public name: string) {}
}
const schema = s.instance(User);
schema.parse(new User('Sapphire'));
schema.parse('oops' as any);
Function validation is not yet implemented and will be made available starting v2.1.0
Functions // TODO
You can define function schemas. This checks for whether or not an input is a function:
s.function;
You can define arguments by passing an array as the first argument, as well as the return type as the second:
s.function([s.string]);
s.function([s.string, s.number], s.string);
Note: ShapeShift will transform the given function into one with validation on arguments and output. You can access the .raw
property of the function to get the unchecked function.
TypedArray
const typedArray = s.typedArray();
const int16Array = s.int16Array;
const uint16Array = s.uint16Array;
const uint8ClampedArray = s.uint8ClampedArray;
const int16Array = s.int16Array;
const uint16Array = s.uint16Array;
const int32Array = s.int32Array;
const uint32Array = s.uint32Array;
const float32Array = s.float32Array;
const float64Array = s.float64Array;
const bigInt64Array = s.bigInt64Array;
const bigUint64Array = s.bigUint64Array;
ShapeShift includes a handful of validations specific to typed arrays.
s.typedArray().lengthLt(5);
s.typedArray().lengthLe(5);
s.typedArray().lengthGt(5);
s.typedArray().lengthGe(5);
s.typedArray().lengthEq(5);
s.typedArray().lengthNe(5);
s.typedArray().lengthRange(0, 4);
s.typedArray().lengthRangeInclusive(0, 4);
s.typedArray().lengthRangeExclusive(0, 4);
Note that all of these methods have analogous methods for working with the typed array's byte length, s.typedArray().byteLengthX()
- for instance, s.typedArray().byteLengthLt(5)
is the same as s.typedArray().lengthLt(5)
but for the array's byte length.
BaseValidator: methods and properties
All schemas in ShapeShift contain certain methods.
.run(data: unknown): Result<T, Error>
: given a schema, you can call this method to check whether or not the input is valid. If it is, a Result
with success: true
and a deep-cloned value will be returned with the given constraints and transformations. Otherwise, a Result
with success: false
and an error is returned.
.parse(data: unknown): T
: given a schema, you can call this method to check whether or not the input is valid. If it is, a deep-cloned value will be returned with the given constraints and transformations. Otherwise, an error is thrown.
.transform<R>((value: T) => R): NopValidator<R>
: adds a constraint that modifies the input:
import { s } from '@sapphire/shapeshift';
const getLength = s.string.transform((value) => value.length);
getLength.parse('Hello There');
Reshape is not yet implemented and will be made available starting v2.1.0
:warning: .transform
's functions must not throw. If a validation error is desired to be thrown, .reshape
instead.
.reshape<R>((value: T) => Result<R, Error> | IConstraint): NopValidator<R>
: adds a constraint able to both validate and modify the input:
import { s, Result } from '@sapphire/shapeshift';
const getLength = s.string.reshape((value) => Result.ok(value.length));
getLength.parse('Hello There');
:warning: .reshape
's functions must not throw. If a validation error is desired to be thrown, use Result.err(error)
instead.
.default(value: T | (() => T))
: transform undefined
into the given value or the callback's returned value:
const name = s.string.default('Sapphire');
name.parse('Hello');
name.parse(undefined);
const number = s.number.default(Math.random);
number.parse(12);
number.parse(undefined);
number.parse(undefined);
:warning: The default values are not validated.
.optional
: a convenience method that returns a union of the type with s.undefined
.
s.string.optional;
.nullable
: a convenience method that returns a union of the type with s.nullable
.
s.string.nullable;
.nullish
: a convenience method that returns a union of the type with s.nullish
.
s.string.nullish;
.array
: a convenience method that returns an ArrayValidator with the type.
s.string.array;
.or
: a convenience method that returns an UnionValidator with the type. This method is also overridden in UnionValidator to just append one more entry.
s.string.or(s.number);
s.object({ name: s.string }).or(s.string, s.number);
Buy us some doughnuts
Sapphire Community is and always will be open source, even if we don't get donations. That being said, we know there are amazing people who may still want to donate just to show their appreciation. Thank you very much in advance!
We accept donations through Open Collective, Ko-fi, Paypal, Patreon and GitHub Sponsorships. You can use the buttons below to donate through your method of choice.
Contributors ✨
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!